#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<fcntl.h>

#define LEFT "["
#define RIGHT "]"
#define PROMPT "#"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define DELIMITER " \t\n"
#define EXIT_CODE 44
#define NONE -1
#define INREDIR 0
#define OUTREDIR 1
#define APPREDIR 2

char commandline[LINE_SIZE];
char* argv[ARGC_SIZE];
int laststatus = 0;
extern char** environ; 
int quit = 0;
char* my_envb[LINE_SIZE] = {NULL};
int env_sub = 0;
char* rdirfilename = NULL;
int rdir = NONE;

char* GetUser()
{
    return getenv("USER");
}

char* GetHostName()
{
    return getenv("HOSTNAME");
}

char* GetPWD(char** pwd)
{
    char* str = getenv("PWD");
    *pwd = (char*)malloc(strlen(str) + 1);
    //strtok会改变他操作的字符串，所以提起记录下最开始开空间的位置，给free用
    char* ret = *pwd;
    strcpy(*pwd, str);
    //strtok会改变传给他的字符串的内容，要用副本供他操作
    char *tmp = strtok(*pwd, "/");
    while(tmp = strtok(NULL, "/"))
    {
        *pwd = tmp;
    }
    return ret;
}

int main()
{
    while(!quit)
    {
        //0.清理每次循环都要用的变量
        rdirfilename = NULL;
        rdir = NONE;

        //1.1.接收输入的命令
        char* pwd = NULL;
        char* fr = GetPWD(&pwd);
        printf(LEFT"%s@%s %s"RIGHT""PROMPT, GetUser(), GetHostName(), pwd);
        free(fr);
        fgets(commandline, sizeof(commandline), stdin); //scan遇到' '就不读了，所以用fgets
        commandline[strlen(commandline) - 1] = '\0'; //fgets会把'\n'也读进来

        //1.2.扫描重定向符号，做拆分
        char* pos = commandline;
        while(*pos)
        {
            if(*pos == '>')
            {
                if(*(pos + 1) == '>')
                {
                    *pos++ = '\0';
                    *pos++ = '\0';
                    while(*pos == ' ')
                    {
                        pos++;
                    }
                    rdirfilename = pos;
                    rdir = APPREDIR;
                }
                else
                {
                    *pos++ = '\0';
                    while(*pos == ' ')
                    {
                        pos++;
                    }
                    rdirfilename = pos;
                    rdir = OUTREDIR;
                }
            }
            else if(*pos == '<')
            {
                *pos++ = '\0';
                while(*pos == ' ')
                {
                    pos++;
                }
                rdirfilename = pos;
                rdir = INREDIR;          
            }
            pos++;
        }
        

        //2.拆分接收到的命令
        int argc = 0;
        argv[argc++] = strtok(commandline, DELIMITER);
        while(argv[argc++] = strtok(NULL, DELIMITER));
        argc--; //while退出的一次，也会argc也会加一次，所以多了一次
        if(argv[0] == NULL) continue; //什么也不输入特殊处理，此时argc为1，所以不能用argc == 0判断


        //3.指令判断
        //内建命令实现重定向只要先修改文件描述符，后改回来即可
        if(argc == 2 && strcmp(argv[0], "cd") == 0)
        {
            chdir(argv[1]);
            setenv("PWD", argv[1], 1);
            continue;
        }
        else if(argc == 1 && strcmp(argv[0], "pwd") == 0)
        {
            printf("%s\n", getenv("PWD"));
            continue;
        }
        else if(argc == 2 && strcmp(argv[0], "echo") == 0)
        {
            if(strcmp(argv[1], "$?") == 0)
            {
                printf("%d\n", laststatus);
                laststatus = 0;
            }
            else if(*argv[1] == '$') //argv[1]代表整个字符串，*argv[1]就代表这个字符串的首字母
            {   char* val = getenv(argv[1] + 1);
                printf("%s\n", val);
            }
            else
            {
                printf("%s\n", argv[1]);
            }
            continue;
        }
        else if(argc == 2 && strcmp(argv[0], "export") == 0)
        {
            //开空间和拷贝一般一起出现
            my_envb[env_sub] = (char*)malloc(strlen(argv[1]) + 1);
            strcpy(my_envb[env_sub], argv[1]);
            putenv(my_envb[env_sub++]);
            continue;
        }
        else if(strcmp(argv[0], "ls") == 0)
        {
            argv[argc++] = "--color";
            argv[argc] = NULL;
        }
        else if(argc == 1 && strcmp(argv[0], "quit") == 0)
        {
            quit = 1;
            continue;
        }
        
        
        //4.非内键命令创建子进程执行
        pid_t ret = fork();
        if(ret == 0)
        {   
            //对子进程中的0，1文件描述符进行了修改，并不影响父进程
            if(rdir != NONE)
            {
                if(rdir == INREDIR)
                {
                    int ret = open(rdirfilename, O_RDWR);
                    dup2(ret, 0);
                }
                else if(rdir == OUTREDIR)
                {
                    int ret = open(rdirfilename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
                    dup2(ret, 1);
                }
                else if(rdir == APPREDIR)
                {
                    int ret = open(rdirfilename, O_WRONLY | O_CREAT | O_APPEND, 0666);
                    dup2(ret, 1);
                }
            }
            execvpe(argv[0], argv, environ);
            //能执行到这里说明程序替换失败了，可以加上提示，如没有这个指令等
            exit(EXIT_CODE);
        }
        else if(ret > 0)
        {
            int status = 0;
            size_t PID = waitpid(ret, &status, 0);
            if(PID < 0)
            {
                printf("子进程回收失败。\n");
            }
            else if(PID > 0)
            {
                laststatus =  WEXITSTATUS(status);
            }
        }
        else
        {
            perror("fork");
            exit(11);
        }
    }
    
    for(int i = 0; my_envb[i]; i++)
    {
        free(my_envb[i]);
    }
    return 0;
}
